package com.hqd.jjvm.instructions;

import com.hqd.jjvm.rtda.JThread;
import com.hqd.jjvm.rtda.StackFrame;
import com.hqd.jjvm.rtda.heap.JMethod;
import com.hqd.jjvm.rtda.heap.RuntimeConstantPool;
import com.hqd.jjvm.rtda.obj.JObject;
import com.hqd.jjvm.rtda.slot.RefSlot;
import com.hqd.jjvm.rtda.slot.Slot;
import com.hqd.jjvm.util.HexStrTransformUtil;
import lombok.Getter;

import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

@Getter
public abstract class AbstractInstruction implements Instruction {

    protected Integer parseAndReadVal(int offer, int length, boolean isAutoMovePc) {
        int index = jThread.getNextPc().get() << 1;
        if (isAutoMovePc) {
            offerIncrement(length);
        } else {
            offerIncrement(-1);
        }
        return HexStrTransformUtil.parseHexToInt(code.substring(index + offer, index + offer + (length << 1)));
    }

    protected Integer parseAndReadVal(int length, boolean isAutoMovePc) {
        return parseAndReadVal(2, length, isAutoMovePc);
    }

    protected Integer parseAndReadValU1(boolean isAutoMovePc) {
        return parseAndReadVal(1, isAutoMovePc);
    }

    protected Integer parseAndReadValU1() {
        return parseAndReadVal(1, true);
    }

    protected Integer parseAndReadValU2(boolean isAutoMovePc) {
        return parseAndReadVal(2, isAutoMovePc);
    }

    protected Integer parseAndReadValU2() {
        return parseAndReadVal(2, true);
    }

    protected Integer parseAndReadValU4(boolean isAutoMovePc) {
        return parseAndReadVal(4, isAutoMovePc);
    }

    protected Integer parseAndReadValU4() {
        return parseAndReadVal(4, true);
    }

    private String code;
    private JMethod jMethod;
    private JThread jThread;
    private AtomicBoolean isRun;

    public AbstractInstruction(JThread jThread) {
        this.jThread = jThread;
        this.jMethod = jThread.getJvmStack().getTop().getJMethod();
        this.code = this.jMethod.getCode().getHexStr();
        this.isRun = new AtomicBoolean(true);
    }

    @Override
    public void execute() {
        Integer instructionCode = parseAndReadValU1();
        InstructionType instructionType = InstructionType.getType(instructionCode);
        execute(instructionType);
        offerIncrement();
    }

    public abstract void execute(InstructionType instructionType);

    protected void pushOperandStackVal(float val) {
        this.jThread.getJvmStack().pushOperandStackFloatVal(val);
    }

    protected void pushOperandStackVal(double val) {
        this.jThread.getJvmStack().pushOperandStackDoubleVal(val);
    }

    protected void pushOperandStackVal(long val) {
        this.jThread.getJvmStack().pushOperandStackLongVal(val);
    }

    protected void pushOperandStackVal(int val) {
        this.jThread.getJvmStack().pushOperandStackIntVal(val);
    }


    protected void pushOperandStackVal(JObject val) {
        this.jThread.getJvmStack().pushOperandStackRefVal(val);
    }


    protected Integer popOperandStackIntVal() {
        return this.jThread.getJvmStack().popOperandStackIntVal();
    }

    protected Float popOperandStackFloatVal() {
        return this.jThread.getJvmStack().popOperandStackFloatVal();
    }

    protected Long popOperandStackLongVal() {
        return this.jThread.getJvmStack().popOperandStackLongVal();
    }

    protected Double popOperandStackDoubleVal() {
        return this.jThread.getJvmStack().popOperandStackDoubleVal();
    }

    protected JObject popOperandStackRefVal() {
        return this.jThread.getJvmStack().popOperandStackRefVal();
    }

    protected Object popOperandStackVal() {
        return this.jThread.getJvmStack().popOperandStackVal();
    }

    protected Slot popSlot() {
        return this.jThread.getJvmStack().getTop().pop();
    }

    protected void pushSlot(Slot slot) {
        this.jThread.getJvmStack().getTop().push(slot);
    }

    protected void offerIncrement() {
        this.jThread.getNextPc().getAndIncrement();
    }

    protected void offerIncrement(Integer count) {
        this.jThread.getNextPc().getAndAdd(count);
    }

    protected void restPcNext() {
        this.jThread.setNextPc(new AtomicInteger(-1));
    }

    protected Long getLocalVarsLongVal(Integer index) {
        return this.jThread.getJvmStack().getLocalVarsLongVal(index);
    }

    protected Integer getLocalVarsIntVal(Integer index) {
        return this.jThread.getJvmStack().getLocalVarsIntVal(index);
    }

    protected Double getLocalVarsDoubleVal(Integer index) {
        return this.jThread.getJvmStack().getLocalVarsDoubleVal(index);
    }

    protected Float getLocalVarsFloatVal(Integer index) {
        return this.jThread.getJvmStack().getLocalVarsFloatVal(index);
    }

    protected JObject getLocalVarsRefVal(Integer index) {
        return this.jThread.getJvmStack().getLocalVarsRefVal(index);
    }

    protected void setLocalVarsIntVal(int index, int val) {
        jThread.getJvmStack().setLocalVarsIntVal(index, val);
    }

    protected void setLocalVarsFloatVal(int index, float val) {
        jThread.getJvmStack().setLocalVarsFloatVal(index, val);
    }

    protected void setLocalVarsLongVal(int index, long val) {
        jThread.getJvmStack().setLocalVarsLongVal(index, val);
    }

    protected void setLocalVarsDoubleVal(int index, double val) {
        jThread.getJvmStack().setLocalVarsDoubleVal(index, val);
    }

    protected void setLocalVarsRefVal(int index, JObject val) {
        jThread.getJvmStack().setLocalVarsRefVal(index, val);
    }

    protected Object peek() {
        return jThread.getJvmStack().getTop().getOperandStack().peekVal();
    }

    protected void pushOperandStackObjVal(Object o) {
        if (o == null) {
            pushSlot(new RefSlot());
        } else {
            if (o instanceof Integer) {
                pushOperandStackVal((Integer) o);
            } else if (o instanceof Float) {
                pushOperandStackVal((Float) o);
            } else if (o instanceof Long) {
                pushOperandStackVal((Long) o);
            } else if (o instanceof Double) {
                pushOperandStackVal((Double) o);
            } else if (o instanceof JObject) {
                pushOperandStackVal((JObject) o);
            } else {
                throw new RuntimeException("未知类型");
            }
        }
    }

    protected Object getOperandStackVal(int offer) {
        Stack<Slot> stack = getStackFrame().getOperandStack().getStack();
        return stack.elementAt(stack.size() - offer).getVal();
    }

    protected void end() {
        this.isRun.set(false);
    }

    protected StackFrame getStackFrame() {
        return jThread.getJvmStack().getTop();
    }

    protected RuntimeConstantPool getRuntimeConstantPool() {
        return jMethod.getJClass().getConstantPool();
    }

}
